﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace MPlusTree
{
    public class MPNode<Key>
        where Key:MPKey, new()
    {
        private MPTree<Key> tree;
        private MPEntry<Key>[] entries;
        private int level;
        private int maxEntries;
        private int numEntries;
        private ulong parentPage;
        private int parentPos;
        private ulong siblingPage;
        private ulong page;
        private MPNode<Key> parentNode;

        public static int EvalNumEntries(int entrySize)
        {
            return (MPStore.GetPageSize() - 40) / entrySize;
        }

        public void Pack(byte[] buff)
        {
            int index = 0;
            byte[] tmp;

            tmp = BitConverter.GetBytes(level);
            Array.Copy(tmp, 0, buff, index, tmp.Length);
            index += tmp.Length;

            tmp = BitConverter.GetBytes(maxEntries);
            Array.Copy(tmp, 0, buff, index, tmp.Length);
            index += tmp.Length;

            tmp = BitConverter.GetBytes(numEntries);
            Array.Copy(tmp, 0, buff, index, tmp.Length);
            index += tmp.Length;

            tmp = BitConverter.GetBytes(parentPage);
            Array.Copy(tmp, 0, buff, index, tmp.Length);
            index += tmp.Length;

            tmp = BitConverter.GetBytes(parentPos);
            Array.Copy(tmp, 0, buff, index, tmp.Length);
            index += tmp.Length;

            tmp = BitConverter.GetBytes(siblingPage);
            Array.Copy(tmp, 0, buff, index, tmp.Length);
            index += tmp.Length;

            tmp = BitConverter.GetBytes(page);
            Array.Copy(tmp, 0, buff, index, tmp.Length);
            index += tmp.Length;
 
            int i;
            for (i = 0; i < numEntries; ++i)
            {
                entries[i].ToByte(buff, index);
                index += entries[i].Size();
            }
        }

        public void UnPack(byte[] buff)
        {
            int index = 0;
            level = BitConverter.ToInt32(buff, index);
            index += 4;

            maxEntries = BitConverter.ToInt32(buff, index);
            index += 4;

            numEntries = BitConverter.ToInt32(buff, index);
            index += 4;

            parentPage = BitConverter.ToUInt64(buff, index);
            index += 8;

            parentPos = BitConverter.ToInt32(buff, index);
            index += 4;

            siblingPage = BitConverter.ToUInt64(buff, index);
            index += 8;

            page = BitConverter.ToUInt64(buff, index);
            index += 8;

            //System.Console.WriteLine(level + "," + numEntries + "," + maxEntries + "," +parentPage);

            entries = new MPEntry<Key>[maxEntries];
            int i;
            for (i = 0; i < numEntries; ++i)
            {
                entries[i] = new MPEntry<Key>();
                entries[i].LoadFrom(buff, index);
                entries[i].SetNode(this);

                index += entries[i].Size();
            }
        }

        public MPNode()
        {
            parentNode = null;
            parentPage = 0;
            parentPos = -1;
            siblingPage = 0;
            level = 0;
            numEntries = 0;
            maxEntries = 0;
            entries = null;
            tree = null;
            page = 0;
        }

        public MPNode(int maxEntries)
        {
            parentNode = null;
            parentPage = 0;
            parentPos = -1;
            siblingPage = 0;
            level = 0;
            numEntries = 0;
            this.maxEntries = maxEntries;
            entries = new MPEntry<Key>[maxEntries];
            tree = null;
            page = 0;
        }

        public MPNode(int maxEntries, int level)
        {
            parentNode = null;
            parentPage = 0;
            parentPos = -1;
            siblingPage = 0;
            this.level = level;
            numEntries = 0;
            this.maxEntries = maxEntries;
            entries = new MPEntry<Key>[maxEntries];
            tree = null;
            page = 0;
        }

        public void SetTree(MPTree<Key> t)
        {
            tree = t;
        }

        public MPTree<Key> GetTree()
        {
            return tree;
        }

        public void SetMaxNumEntries(int num)
        {
            maxEntries = num;
            entries = new MPEntry<Key>[maxEntries];
        }

        public int GetMaxNumEntries()
        {
            return maxEntries;
        }

        public ulong GetParentPage()
        {
            return parentPage;
        }

        public void SetParentPage(ulong p)
        {
            parentPage = p;

            if (numEntries > 0)
            {
                if (tree != null && parentPage != 0 && parentPos != -1)
                {
                    parentNode = tree.ReadNode(parentPage);
                    MPEntry<Key> parentEntry = parentNode.GetEntries()[parentPos];
                    int i;
                    for (i = 0; i < numEntries; ++i)
                    {
                        //Update distance to parent of enties[i]
                        entries[i].SetDisToParent(entries[i].GetKey().Distance(parentEntry.GetKey()));

                        /*
                        //Update enties[i].left, entries[i].right
                        if (tree != null)
                        {
                            MPNode<Key> node;
                            //left
                            if (entries[i].GetLeftPage() != 0)
                            {
                                node = tree.ReadNode(entries[i].GetLeftPage());
                                node.SetParent(parentPage, parentPos);
                                tree.WriteNode(node);
                            }
                            //right
                            if (entries[i].GetRightPage() != 0)
                            {
                                node = tree.ReadNode(entries[i].GetRightPage());
                                node.SetParent(parentPage, parentPos);
                                tree.WriteNode(node);
                            }
                        }
                         * */
                    }
                }
            }
        }

        public void SetParentPos(int pos)
        {
            parentPos = pos;

            if (numEntries > 0)
            {
                if (tree != null && parentPage != 0 && parentPos != -1)
                {
                    parentNode = tree.ReadNode(parentPage);
                    MPEntry<Key> parentEntry = parentNode.GetEntries()[parentPos];
                    int i;
                    for (i = 0; i < numEntries; ++i)
                    {
                        //Update distance to parent of enties[i]
                        entries[i].SetDisToParent(entries[i].GetKey().Distance(parentEntry.GetKey()));

                        /*
                        //Update enties[i].left, entries[i].right
                        if (tree != null)
                        {
                            MPNode<Key> node;
                            //left
                            if (entries[i].GetLeftPage() != 0)
                            {
                                node = tree.ReadNode(entries[i].GetLeftPage());
                                node.SetParent(parentPage, parentPos);
                                tree.WriteNode(node);
                            }
                            //right
                            if (entries[i].GetRightPage() != 0)
                            {
                                node = tree.ReadNode(entries[i].GetRightPage());
                                node.SetParent(parentPage, parentPos);
                                tree.WriteNode(node);
                            }
                        }
                         * */
                    }
                }
            }
        }

        public void SetParent(ulong page, int pos)
        {
            parentPage = page;
            parentPos = pos;

            if (numEntries > 0)
            {
                if (tree != null && parentPage != 0 && parentPos != -1)
                {
                    parentNode = tree.ReadNode(parentPage);
                    MPEntry<Key> parentEntry = parentNode.GetEntries()[parentPos];
                    int i;
                    for (i = 0; i < numEntries; ++i)
                    {
                        //Update distance to parent of enties[i]
                        entries[i].SetDisToParent(entries[i].GetKey().Distance(parentEntry.GetKey()));

                        /*
                        //Update enties[i].left, entries[i].right
                        if (tree != null)
                        {
                            MPNode<Key> node;
                            //left
                            if (entries[i].GetLeftPage() != 0)
                            {
                                node = tree.ReadNode(entries[i].GetLeftPage());
                                node.SetParent(parentPage, parentPos);
                                tree.WriteNode(node);
                            }
                            //right
                            if (entries[i].GetRightPage() != 0)
                            {
                                node = tree.ReadNode(entries[i].GetRightPage());
                                node.SetParent(parentPage, parentPos);
                                tree.WriteNode(node);
                            }
                        }
                         * */
                    }
                }
            }
        }

        public void SetParent(MPEntry<Key> parent)
        {
            if (parent != null)
            {
                parentNode = parent.GetNode();
                parentPos = parent.GetPos();

                if (parentNode != null)
                {
                    parentPage = parentNode.GetPage();

                    if (numEntries > 0)
                    {
                        if (parentNode != null)
                        {
                            int i;
                            for (i = 0; i < numEntries; ++i)
                            {
                                //Update distance to parent of enties[i]
                                entries[i].SetDisToParent(entries[i].GetKey().Distance(parent.GetKey()));

                                /*
                                //Update enties[i].left, entries[i].right
                                if (tree != null)
                                {
                                    MPNode<Key> node;
                                    //left
                                    if (entries[i].GetLeftPage() != 0)
                                    {
                                        node = tree.ReadNode(entries[i].GetLeftPage());
                                        node.SetParent(parentPage, parentPos);
                                        tree.WriteNode(node);
                                    }
                                    //right
                                    if (entries[i].GetRightPage() != 0)
                                    {
                                        node = tree.ReadNode(entries[i].GetRightPage());
                                        node.SetParent(parentPage, parentPos);
                                        tree.WriteNode(node);
                                    }
                                }
                                 * */
                            }
                        }
                        else if (tree != null && parentPage != 0 && parentPos != -1)
                        {
                            parentNode = tree.ReadNode(parentPage);
                            int i;
                            for (i = 0; i < numEntries; ++i)
                            {
                                //Update distance to parent of enties[i]
                                entries[i].SetDisToParent(entries[i].GetKey().Distance(parent.GetKey()));

                                /*
                                //Update enties[i].left, entries[i].right
                                if (tree != null)
                                {
                                    MPNode<Key> node;
                                    //left
                                    if (entries[i].GetLeftPage() != 0)
                                    {
                                        node = tree.ReadNode(entries[i].GetLeftPage());
                                        node.SetParent(parentPage, parentPos);
                                        tree.WriteNode(node);
                                    }
                                    //right
                                    if (entries[i].GetRightPage() != 0)
                                    {
                                        node = tree.ReadNode(entries[i].GetRightPage());
                                        node.SetParent(parentPage, parentPos);
                                        tree.WriteNode(node);
                                    }
                                }
                                 * */
                            }
                        }
                    }
                }
            }
        }

        public int GetParentPos()
        {
            return parentPos;
        }

        public void SetPage(ulong page)
        {
            this.page = page;
        }

        public ulong GetPage()
        {
            return page;
        }

        public int GetNumEntries()
        { 
            return numEntries; 
        }

        public void SetNumEntries(int num)
        {
            numEntries = num;
        }

        public ulong GetSiblingPage()
        {
            return siblingPage; 
        }
        
        public void SetSiblingPage(ulong p)
        {
            siblingPage = p; 
        }

        public bool IsLeaf()
        {
            return level == 0;
        }

        public int GetLevel()
        {
            return level;
        }
        
        public void SetLevel(int l) 
        {
            level = l; 
        }

        public MPEntry<Key>[] GetEntries()
        {
            return entries;
        }

        public bool IsFull()
        {
            return numEntries == maxEntries;
        }

        public bool Replace(MPEntry<Key> entry, int posToReplace)
        {
            if (posToReplace < numEntries)
            {
                entries[posToReplace] = entry;
                entry.SetPos(posToReplace);
                entry.SetNode(this);

                if (tree != null && parentNode == null && parentPage != 0 && parentPos != -1)
                {
                    parentNode = tree.ReadNode(parentPage);
                    entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
                }
                else if (parentNode != null)
                {
                    entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
                }

                //Update entry.left, entry.right
                if (tree != null)
                {
                    MPNode<Key> node;
                    //left
                    if (entry.GetLeftPage() != 0)
                    {
                        node = tree.ReadNode(entry.GetLeftPage());
                        node.SetParent(page, posToReplace);
                        tree.WriteNode(node);
                    }
                    //right
                    if (entry.GetRightPage() != 0)
                    {
                        node = tree.ReadNode(entry.GetRightPage());
                        node.SetParent(page, posToReplace);
                        tree.WriteNode(node);
                    }
                }

                return true;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }

        public bool Replace(MPEntry<Key> entry, int posToReplace, bool isAdjustRadius)
        {
            if (isAdjustRadius == false)
            {
                return Replace(entry, posToReplace);
            }
            else
            {
                if (posToReplace < numEntries)
                {
                    entries[posToReplace] = entry;
                    entry.SetPos(posToReplace);
                    entry.SetNode(this);

                    if (tree != null && parentNode == null && parentPage != 0 && parentPos != -1)
                    {
                        parentNode = tree.ReadNode(parentPage);
                        entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
                    }
                    else if (parentNode != null)
                    {
                        entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
                    }

                    //Update entry.left, entry.right
                    if (tree != null)
                    {
                        MPNode<Key> node;
                        MPEntry<Key>[] entr;
                        int num;
                        int i;

                        //left
                        if (entry.GetLeftPage() != 0)
                        {
                            node = tree.ReadNode(entry.GetLeftPage());
                            node.SetParent(entry);

                            num = node.GetNumEntries();
                            entr = node.GetEntries();
                            for (i = 0; i < num; ++i)
                            {
                                entr[i].SetDisToParent(entr[i].GetKey().Distance(entry.GetKey()));

                                if (entry.GetRadius() < entr[i].GetDisToParent() + entr[i].GetRadius())
                                    entry.SetRadius(entr[i].GetDisToParent() + entr[i].GetRadius());
                            }

                            tree.WriteNode(node);
                        }

                        //right
                        if (entry.GetRightPage() != 0)
                        {
                            node = tree.ReadNode(entry.GetRightPage());
                            node.SetParent(entry);

                            num = node.GetNumEntries();
                            entr = node.GetEntries();
                            for (i = 0; i < num; ++i)
                            {
                                entr[i].SetDisToParent(entr[i].GetKey().Distance(entry.GetKey()));

                                if (entry.GetRadius() < entr[i].GetDisToParent() + entr[i].GetRadius())
                                    entry.SetRadius(entr[i].GetDisToParent() + entr[i].GetRadius());
                            }

                            tree.WriteNode(node);
                        }
                    }

                    return true;
                }
                else
                {
                    throw new IndexOutOfRangeException();
                }
            }
        }

        public void AddEntry(MPEntry<Key> entry)
        {
            if (IsFull()) throw new IndexOutOfRangeException();

            entries[numEntries] = entry;
            entry.SetPos(numEntries);
            entry.SetNode(this);

            //Update distance to parent
            if (tree != null && parentNode == null && parentPage != 0 && parentPos != -1)
            {
                parentNode = tree.ReadNode(parentPage);
                entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
            }
            else if (parentNode != null)
            {
                entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
            }

            //Update entry.left, entry.right
            if (tree != null)
            {
                MPNode<Key> node;
                //left
                if (entry.GetLeftPage() != 0)
                {
                    node = tree.ReadNode(entry.GetLeftPage());
                    node.SetParent(entry);
                    tree.WriteNode(node);
                }
                //right
                if (entry.GetRightPage() != 0)
                {
                    node = tree.ReadNode(entry.GetRightPage());
                    node.SetParent(entry);
                    tree.WriteNode(node);
                }
            }

            ++numEntries;
        }

        public void AddEntry(MPEntry<Key> entry, bool isAdjustRadius)
        {
            if (isAdjustRadius == false)
            {
                AddEntry(entry);
            }
            else
            {
                if (IsFull()) throw new IndexOutOfRangeException();

                entries[numEntries] = entry;
                entry.SetPos(numEntries);
                entry.SetNode(this);

                //Update distance to parent
                if (tree != null && parentNode == null && parentPage != 0 && parentPos != -1)
                {
                    parentNode = tree.ReadNode(parentPage);
                    entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
                }
                else if (parentNode != null)
                {
                    entry.SetDisToParent(entry.GetKey().Distance(parentNode.entries[parentPos].GetKey()));
                }

                //Update entry.left, entry.right
                if (tree != null)
                {
                    MPNode<Key> node;
                    MPEntry<Key>[] entr;
                    int num;
                    int i;

                    //left
                    if (entry.GetLeftPage() != 0)
                    {
                        node = tree.ReadNode(entry.GetLeftPage());
                        node.SetParent(entry);

                        num = node.GetNumEntries();
                        entr = node.GetEntries();
                        for (i = 0; i < num; ++i)
                        {
                            entr[i].SetDisToParent(entr[i].GetKey().Distance(entry.GetKey()));

                            if (entry.GetRadius() < entr[i].GetDisToParent() + entr[i].GetRadius())
                                entry.SetRadius(entr[i].GetDisToParent() + entr[i].GetRadius());
                        }

                        tree.WriteNode(node);
                    }

                    //right
                    if (entry.GetRightPage() != 0)
                    {
                        node = tree.ReadNode(entry.GetRightPage());
                        node.SetParent(entry);

                        num = node.GetNumEntries();
                        entr = node.GetEntries();
                        for (i = 0; i < num; ++i)
                        {
                            entr[i].SetDisToParent(entr[i].GetKey().Distance(entry.GetKey()));

                            if (entry.GetRadius() < entr[i].GetDisToParent() + entr[i].GetRadius())
                                entry.SetRadius(entr[i].GetDisToParent() + entr[i].GetRadius());
                        }

                        tree.WriteNode(node);
                    }
                }

                ++numEntries;
            }
        }

        public void DelEntry(int index)
        {
            if (index >= numEntries) throw new IndexOutOfRangeException();

            int i;
            int max = numEntries - 1;
            for (i = index; i < max; ++i)
            {
                entries[i] = entries[i + 1];
                entries[i].SetPos(i);
            }
        }
    }
}
